Skip to content

dotbot: report every robot a camera sees, paced to a share of a core - #300

Merged
geonnave merged 11 commits into
DotBots:mainfrom
geonnave:camera-multi
Sep 24, 2026
Merged

geonnave merged 11 commits into
DotBots:mainfrom
geonnave:camera-multi

Conversation

@geonnave

Copy link
Copy Markdown
Contributor

The camera detector used to report one robot per frame. It now fits every robot-shaped candidate on a frame and names each from the robots' lighthouse (LH2) fixes, reporting up to camera_max_robots of them (2 by default). Robots whose fix stands on a candidate are kept first. The console draws every camera robot and its heading, on the map and in the inspector.

Detection rate held to a share of a core

Detection cost grows with the number of robots on the frame, so a fixed rate either wastes the idle case or saturates the busy one. The detector now measures its own cost (smoothed) and schedules the next run so that it holds, on average, camera_detect_share of one core (0.4 by default): rate = share / cost, capped at 10 Hz and floored at 0.5 Hz. The rate falls as robots are added or the machine gets busy and recovers when either goes away.

Measured cost on a real bench frame:

Robots on frame Detection cost Rate at share 0.4
0 18 ms 10 Hz (capped)
1 about 117 ms about 3.4 Hz
2 about 296 ms about 1.4 Hz

OpenCV runs some of this multithreaded, so process CPU is about 1.2x the wall time measured here; the share is a wall-time budget, not an exact CPU cap.

Both knobs are [controller] config keys and dotbot run controller flags (--camera-max-robots, --camera-detect-share), bounded to >= 1 and (0, 1].

Naming robots correctly

Review of the first cut turned up ways two robots could swap or copy names, fixed here:

  • Whole-set matching. Fixes are matched to candidates as one assignment instead of greedily nearest-first. Greedy pairing let a lagging fix take its neighbour's candidate and left the neighbour's fix to split it, so the two robots swapped names. A fix that lands on a component too small for two robots now names nothing instead of copying its host's pose.
  • Stale fixes and silent robots. A robot is only marked lost after 60 s of silence, and until then its last fix could name whichever robot now stands where it was. Only fixes from the last 2 s, from robots still advertising, name a candidate.
  • Atomic position read. Each fix is read once per frame, since another thread replaces the position whole.
  • Config bounds on the two new keys, so a bad value fails at load rather than inside the detector.

Frame capture time

Each part of the MJPEG camera stream now carries an X-Timestamp header, the time the frame was read off the device. A reader slower than the stream builds a backlog in the socket buffers that can be seconds deep, and stamping on receipt hides it; with the capture time a reader can age each frame and drop stale ones.

Breaking change

A camera detection record carries robots, a list of {address, status, timestamp, pose}, in place of the single pose. The camera CSV writes one row per robot with cam_address, cam_status and cam_timestamp under schema_version 2, so an older log is not appended to. Anything reading the old pose field or the schema 1 CSV needs updating.

Changes

File + -
dotbot/camera/detection/robot.py +361 -25
dotbot/camera/detection/pose.py +85 -28
dotbot/camera/rate.py +86 -0
dotbot/camera/service.py +70 -11
dotbot/controller.py +67 -27
dotbot/controller_app.py +39 -0
dotbot/csv_data_logger.py +28 -12
dotbot/models.py +25 -11
dotbot/console-web/src/MapView.tsx +59 -51
dotbot/console-web/src/Inspector.tsx +52 -13
Tests: test_camera_detection.py, test_camera_rate.py, test_camera_service.py, test_controller.py, test_csv_data_logger.py, test_server.py, cameraLayer.test.tsx, inspector.test.ts +685 -88
10 small files: detection/__init__.py, config.py, server.py, test_config.py, RightPane.tsx, cameraLayer.ts, localization.ts, localization.test.ts, types.ts, useFleet.test.ts +108 -30
Total, 28 files +1665 -296

Validation

  • dotbot/tests pass, including new tests for the matcher, the rate controller, the service and the CSV schema. The only local failures are environment ones unrelated to this branch (a user config file present, port 8000 taken).
  • Console: tsc --noEmit, eslint, vitest (578 tests) and vite build pass.
  • Tested live on the bench on 2026-09-24 with a controller running this branch: two robots detected and named from their LH2 fixes, drawn in the console.

Breaking: a camera detection record carries `robots`, a list of
{address, status, timestamp, pose}, in place of the single `pose`. The
camera CSV writes one row per robot with cam_address, cam_status and
cam_timestamp under schema_version 2, so an older log is not appended to.

AI-assisted: Claude Opus 5.5
A greedy nearest-first pairing lets a lagging fix take the neighbour's
candidate and leaves the robot's own fix to split that neighbour, so two
robots swap names. A fix sharing a component too small for two robots
now names nothing instead of copying its host's pose.

AI-assisted: Claude Opus 5.5
A robot is marked lost only after 60 s of silence, and until then its
last fix could name whichever robot now stands where it was. Each fix is
also read once, since the position is replaced whole by another thread.

AI-assisted: Claude Opus 5.5
A reader slower than the stream that keeps every part builds a backlog in
the socket buffers, seconds deep, and stamping at receipt hides it. The
X-Timestamp header lets a reader age each frame and drop stale ones.

AI-assisted: Claude Opus 5.5
@codecov

codecov Bot commented Sep 24, 2026 •

Copy link
Copy Markdown

Codecov Report

❌ Patch coverage is 97.34177% with 21 lines in your changes missing coverage. Please review.
✅ Project coverage is 84.98%. Comparing base (56e2637) to head (20af47d).
⚠️ Report is 1 commits behind head on main.

Files with missing lines Patch % Lines
dotbot/console-web/src/Inspector.tsx 66.66% 13 Missing ⚠️
dotbot/camera/detection/robot.py 97.76% 4 Missing ⚠️
dotbot/camera/detection/pose.py 94.23% 3 Missing ⚠️
dotbot/tests/test_camera_service.py 98.07% 1 Missing ⚠️
Additional details and impacted files

Impacted file tree graph

@@            Coverage Diff             @@
##             main     #300      +/-   ##
==========================================
+ Coverage   84.65%   84.98%   +0.32%     
==========================================
  Files         204      206       +2     
  Lines       25336    26002     +666     
  Branches     1822     1836      +14     
==========================================
+ Hits        21449    22098     +649     
- Misses       3880     3897      +17     
  Partials        7        7              
Flag Coverage Δ
console 75.57% <87.85%> (-0.02%) ⬇️
frontend 97.80% <ø> (ø)
python 86.62% <98.82%> (+0.55%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Files with missing lines Coverage Δ
dotbot/camera/detection/__init__.py 100.00% <100.00%> (ø)
dotbot/camera/rate.py 100.00% <100.00%> (ø)
dotbot/camera/service.py 95.58% <100.00%> (-0.14%) ⬇️
dotbot/config.py 98.27% <100.00%> (+0.02%) ⬆️
dotbot/console-web/src/MapView.tsx 96.73% <100.00%> (+0.02%) ⬆️
dotbot/console-web/src/RightPane.tsx 97.80% <100.00%> (+<0.01%) ⬆️
dotbot/console-web/src/cameraLayer.ts 98.94% <100.00%> (ø)
dotbot/console-web/src/localization.ts 100.00% <100.00%> (ø)
dotbot/console-web/src/types.ts 100.00% <ø> (ø)
dotbot/controller.py 85.71% <100.00%> (+0.65%) ⬆️
... and 14 more
🚀 New features to boost your workflow:
  • ❄️ Test Analytics: Detect flaky tests, report on failures, and find test suite problems.
  • 📦 JS Bundle Analysis: Save yourself from yourself by tracking and limiting bundle sizes in JS merges.

@geonnave
geonnave merged commit 62ec63a into DotBots:main Sep 24, 2026
16 checks passed
@geonnave
geonnave deleted the camera-multi branch September 24, 2026 06:58
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant